package catalognext

import (
	"encoding/json"
	"testing"

	"github.com/goccy/go-yaml"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/docker/mcp-gateway/pkg/catalog"
	"github.com/docker/mcp-gateway/pkg/workingset"
)

func TestListServersNoFilters(t *testing.T) {
	dao := setupTestDB(t)
	ctx := t.Context()

	// Create a catalog with multiple servers
	catalogObj := Catalog{
		Ref: "test/catalog:latest",
		CatalogArtifact: CatalogArtifact{
			Title: "Test Catalog",
			Servers: []Server{
				{
					Type:  workingset.ServerTypeImage,
					Image: "docker/server1:v1",
					Snapshot: &workingset.ServerSnapshot{
						Server: catalog.Server{
							Name:        "server-one",
							Description: "First server",
						},
					},
				},
				{
					Type:  workingset.ServerTypeImage,
					Image: "docker/server2:v1",
					Snapshot: &workingset.ServerSnapshot{
						Server: catalog.Server{
							Name:        "server-two",
							Description: "Second server",
						},
					},
				},
			},
		},
	}

	dbCat, err := catalogObj.ToDb()
	require.NoError(t, err)
	err = dao.UpsertCatalog(ctx, dbCat)
	require.NoError(t, err)

	output := captureStdout(t, func() {
		err := ListServers(ctx, dao, catalogObj.Ref, []string{}, workingset.OutputFormatJSON)
		require.NoError(t, err)
	})

	var result map[string]any
	err = json.Unmarshal([]byte(output), &result)
	require.NoError(t, err)

	assert.Equal(t, catalogObj.Ref, result["catalog"])
	assert.Equal(t, catalogObj.Title, result["title"])
	servers := result["servers"].([]any)
	assert.Len(t, servers, 2)
}

func TestListServersFilterByName(t *testing.T) {
	dao := setupTestDB(t)
	ctx := t.Context()

	catalogObj := Catalog{
		Ref: "test/catalog:latest",
		CatalogArtifact: CatalogArtifact{
			Title: "Test Catalog",
			Servers: []Server{
				{
					Type:  workingset.ServerTypeImage,
					Image: "docker/server1:v1",
					Snapshot: &workingset.ServerSnapshot{
						Server: catalog.Server{
							Name:        "my-server",
							Description: "My server",
						},
					},
				},
				{
					Type:  workingset.ServerTypeImage,
					Image: "docker/server2:v1",
					Snapshot: &workingset.ServerSnapshot{
						Server: catalog.Server{
							Name:        "other-server",
							Description: "Other server",
						},
					},
				},
			},
		},
	}

	dbCat, err := catalogObj.ToDb()
	require.NoError(t, err)
	err = dao.UpsertCatalog(ctx, dbCat)
	require.NoError(t, err)

	output := captureStdout(t, func() {
		err := ListServers(ctx, dao, catalogObj.Ref, []string{"name=my"}, workingset.OutputFormatJSON)
		require.NoError(t, err)
	})

	var result map[string]any
	err = json.Unmarshal([]byte(output), &result)
	require.NoError(t, err)

	servers := result["servers"].([]any)
	assert.Len(t, servers, 1)
}

func TestListServersFilterByNameCaseInsensitive(t *testing.T) {
	dao := setupTestDB(t)
	ctx := t.Context()

	catalogObj := Catalog{
		Ref: "test/catalog:latest",
		CatalogArtifact: CatalogArtifact{
			Title: "Test Catalog",
			Servers: []Server{
				{
					Type:  workingset.ServerTypeImage,
					Image: "docker/server1:v1",
					Snapshot: &workingset.ServerSnapshot{
						Server: catalog.Server{
							Name:        "MyServer",
							Description: "Test server",
						},
					},
				},
			},
		},
	}

	dbCat, err := catalogObj.ToDb()
	require.NoError(t, err)
	err = dao.UpsertCatalog(ctx, dbCat)
	require.NoError(t, err)

	output := captureStdout(t, func() {
		err := ListServers(ctx, dao, catalogObj.Ref, []string{"name=myserver"}, workingset.OutputFormatJSON)
		require.NoError(t, err)
	})

	var result map[string]any
	err = json.Unmarshal([]byte(output), &result)
	require.NoError(t, err)

	servers := result["servers"].([]any)
	assert.Len(t, servers, 1)
}

func TestListServersFilterByNamePartialMatch(t *testing.T) {
	dao := setupTestDB(t)
	ctx := t.Context()

	catalogObj := Catalog{
		Ref: "test/catalog:latest",
		CatalogArtifact: CatalogArtifact{
			Title: "Test Catalog",
			Servers: []Server{
				{
					Type:  workingset.ServerTypeImage,
					Image: "docker/server1:v1",
					Snapshot: &workingset.ServerSnapshot{
						Server: catalog.Server{
							Name:        "my-awesome-server",
							Description: "Test server",
						},
					},
				},
			},
		},
	}

	dbCat, err := catalogObj.ToDb()
	require.NoError(t, err)
	err = dao.UpsertCatalog(ctx, dbCat)
	require.NoError(t, err)

	output := captureStdout(t, func() {
		err := ListServers(ctx, dao, catalogObj.Ref, []string{"name=awesome"}, workingset.OutputFormatJSON)
		require.NoError(t, err)
	})

	var result map[string]any
	err = json.Unmarshal([]byte(output), &result)
	require.NoError(t, err)

	servers := result["servers"].([]any)
	assert.Len(t, servers, 1)
}

func TestListServersFilterNoMatches(t *testing.T) {
	dao := setupTestDB(t)
	ctx := t.Context()

	catalogObj := Catalog{
		Ref: "test/catalog:latest",
		CatalogArtifact: CatalogArtifact{
			Title: "Test Catalog",
			Servers: []Server{
				{
					Type:  workingset.ServerTypeImage,
					Image: "docker/server1:v1",
					Snapshot: &workingset.ServerSnapshot{
						Server: catalog.Server{
							Name:        "my-server",
							Description: "Test server",
						},
					},
				},
			},
		},
	}

	dbCat, err := catalogObj.ToDb()
	require.NoError(t, err)
	err = dao.UpsertCatalog(ctx, dbCat)
	require.NoError(t, err)

	output := captureStdout(t, func() {
		err := ListServers(ctx, dao, catalogObj.Ref, []string{"name=nonexistent"}, workingset.OutputFormatJSON)
		require.NoError(t, err)
	})

	var result map[string]any
	err = json.Unmarshal([]byte(output), &result)
	require.NoError(t, err)

	servers := result["servers"].([]any)
	assert.Empty(t, servers)
}

func TestListServersWithoutSnapshot(t *testing.T) {
	dao := setupTestDB(t)
	ctx := t.Context()

	catalogObj := Catalog{
		Ref: "test/catalog:latest",
		CatalogArtifact: CatalogArtifact{
			Title: "Test Catalog",
			Servers: []Server{
				{
					Type:     workingset.ServerTypeImage,
					Image:    "docker/server1:v1",
					Snapshot: nil, // No snapshot
				},
			},
		},
	}

	dbCat, err := catalogObj.ToDb()
	require.NoError(t, err)
	err = dao.UpsertCatalog(ctx, dbCat)
	require.NoError(t, err)

	output := captureStdout(t, func() {
		err := ListServers(ctx, dao, catalogObj.Ref, []string{"name=test"}, workingset.OutputFormatJSON)
		require.NoError(t, err)
	})

	var result map[string]any
	err = json.Unmarshal([]byte(output), &result)
	require.NoError(t, err)

	// Server without snapshot should not match any name filter
	servers := result["servers"].([]any)
	assert.Empty(t, servers)
}

func TestListServersInvalidFilter(t *testing.T) {
	dao := setupTestDB(t)
	ctx := t.Context()

	catalogObj := Catalog{
		Ref: "test/catalog:latest",
		CatalogArtifact: CatalogArtifact{
			Title: "Test Catalog",
			Servers: []Server{
				{
					Type:  workingset.ServerTypeImage,
					Image: "docker/server1:v1",
				},
			},
		},
	}

	dbCat, err := catalogObj.ToDb()
	require.NoError(t, err)
	err = dao.UpsertCatalog(ctx, dbCat)
	require.NoError(t, err)

	err = ListServers(ctx, dao, catalogObj.Ref, []string{"invalid"}, workingset.OutputFormatJSON)
	require.Error(t, err)
	assert.Contains(t, err.Error(), "invalid filter format")
}

func TestListServersUnsupportedFilterKey(t *testing.T) {
	dao := setupTestDB(t)
	ctx := t.Context()

	catalogObj := Catalog{
		Ref: "test/catalog:latest",
		CatalogArtifact: CatalogArtifact{
			Title: "Test Catalog",
			Servers: []Server{
				{
					Type:  workingset.ServerTypeImage,
					Image: "docker/server1:v1",
				},
			},
		},
	}

	dbCat, err := catalogObj.ToDb()
	require.NoError(t, err)
	err = dao.UpsertCatalog(ctx, dbCat)
	require.NoError(t, err)

	err = ListServers(ctx, dao, catalogObj.Ref, []string{"unsupported=value"}, workingset.OutputFormatJSON)
	require.Error(t, err)
	assert.Contains(t, err.Error(), "unsupported filter key")
}

func TestListServersCatalogNotFound(t *testing.T) {
	dao := setupTestDB(t)
	ctx := t.Context()

	err := ListServers(ctx, dao, "test/nonexistent:latest", []string{}, workingset.OutputFormatJSON)
	require.Error(t, err)
	assert.Contains(t, err.Error(), "failed to get catalog")
}

func TestListServersYAMLFormat(t *testing.T) {
	dao := setupTestDB(t)
	ctx := t.Context()

	catalogObj := Catalog{
		Ref: "test/catalog:latest",
		CatalogArtifact: CatalogArtifact{
			Title: "Test Catalog",
			Servers: []Server{
				{
					Type:  workingset.ServerTypeImage,
					Image: "docker/server1:v1",
					Snapshot: &workingset.ServerSnapshot{
						Server: catalog.Server{
							Name: "server-one",
						},
					},
				},
			},
		},
	}

	dbCat, err := catalogObj.ToDb()
	require.NoError(t, err)
	err = dao.UpsertCatalog(ctx, dbCat)
	require.NoError(t, err)

	output := captureStdout(t, func() {
		err := ListServers(ctx, dao, catalogObj.Ref, []string{}, workingset.OutputFormatYAML)
		require.NoError(t, err)
	})

	var result map[string]any
	err = yaml.Unmarshal([]byte(output), &result)
	require.NoError(t, err)

	assert.Equal(t, catalogObj.Ref, result["catalog"])
	assert.Equal(t, catalogObj.Title, result["title"])
}

func TestListServersHumanReadableFormat(t *testing.T) {
	dao := setupTestDB(t)
	ctx := t.Context()

	catalogObj := Catalog{
		Ref: "test/catalog:latest",
		CatalogArtifact: CatalogArtifact{
			Title: "Test Catalog",
			Servers: []Server{
				{
					Type:  workingset.ServerTypeImage,
					Image: "docker/server1:v1",
					Snapshot: &workingset.ServerSnapshot{
						Server: catalog.Server{
							Name:        "server-one",
							Title:       "Server One",
							Description: "First server",
							Tools: []catalog.Tool{
								{Name: "tool1"},
								{Name: "tool2"},
							},
						},
					},
				},
				{
					Type:   workingset.ServerTypeRegistry,
					Source: "https://example.com/api",
					Snapshot: &workingset.ServerSnapshot{
						Server: catalog.Server{
							Name: "server-two",
						},
					},
				},
				{
					Type:     workingset.ServerTypeRemote,
					Endpoint: "https://remote.example.com",
					Snapshot: &workingset.ServerSnapshot{
						Server: catalog.Server{
							Name: "server-three",
						},
					},
				},
			},
		},
	}

	dbCat, err := catalogObj.ToDb()
	require.NoError(t, err)
	err = dao.UpsertCatalog(ctx, dbCat)
	require.NoError(t, err)

	output := captureStdout(t, func() {
		err := ListServers(ctx, dao, catalogObj.Ref, []string{}, workingset.OutputFormatHumanReadable)
		require.NoError(t, err)
	})

	// Verify human-readable format contains expected elements
	assert.Contains(t, output, "Catalog: "+catalogObj.Ref)
	assert.Contains(t, output, "Title: Test Catalog")
	assert.Contains(t, output, "Servers (3)")
	assert.Contains(t, output, "server-one")
	assert.Contains(t, output, "Title: Server One")
	assert.Contains(t, output, "Description: First server")
	assert.Contains(t, output, "Type: image")
	assert.Contains(t, output, "Image: docker/server1:v1")
	assert.Contains(t, output, "Tools: 2")
	assert.Contains(t, output, "server-two")
	assert.Contains(t, output, "Type: registry")
	assert.Contains(t, output, "Source: https://example.com/api")
	assert.Contains(t, output, "server-three")
	assert.Contains(t, output, "Type: remote")
	assert.Contains(t, output, "Endpoint: https://remote.example.com")
}

func TestListServersHumanReadableNoServers(t *testing.T) {
	dao := setupTestDB(t)
	ctx := t.Context()

	catalogObj := Catalog{
		Ref: "test/catalog:latest",
		CatalogArtifact: CatalogArtifact{
			Title: "Empty Catalog",
			Servers: []Server{
				{
					Type:     workingset.ServerTypeImage,
					Image:    "docker/server1:v1",
					Snapshot: nil, // No snapshot
				},
			},
		},
	}

	dbCat, err := catalogObj.ToDb()
	require.NoError(t, err)
	err = dao.UpsertCatalog(ctx, dbCat)
	require.NoError(t, err)

	output := captureStdout(t, func() {
		err := ListServers(ctx, dao, catalogObj.Ref, []string{"name=nonexistent"}, workingset.OutputFormatHumanReadable)
		require.NoError(t, err)
	})

	assert.Contains(t, output, "No servers found")
}

func TestListServersUnsupportedFormat(t *testing.T) {
	dao := setupTestDB(t)
	ctx := t.Context()

	catalogObj := Catalog{
		Ref: "test/catalog:latest",
		CatalogArtifact: CatalogArtifact{
			Title: "Test Catalog",
			Servers: []Server{
				{
					Type:  workingset.ServerTypeImage,
					Image: "docker/server1:v1",
				},
			},
		},
	}

	dbCat, err := catalogObj.ToDb()
	require.NoError(t, err)
	err = dao.UpsertCatalog(ctx, dbCat)
	require.NoError(t, err)

	err = ListServers(ctx, dao, catalogObj.Ref, []string{}, "unsupported")
	require.Error(t, err)
	assert.Contains(t, err.Error(), "unsupported format")
}

func TestListServersServersSortedByName(t *testing.T) {
	dao := setupTestDB(t)
	ctx := t.Context()

	catalogObj := Catalog{
		Ref: "test/catalog:latest",
		CatalogArtifact: CatalogArtifact{
			Title: "Test Catalog",
			Servers: []Server{
				{
					Type:  workingset.ServerTypeImage,
					Image: "docker/server1:v1",
					Snapshot: &workingset.ServerSnapshot{
						Server: catalog.Server{
							Name: "zebra-server",
						},
					},
				},
				{
					Type:  workingset.ServerTypeImage,
					Image: "docker/server2:v1",
					Snapshot: &workingset.ServerSnapshot{
						Server: catalog.Server{
							Name: "alpha-server",
						},
					},
				},
				{
					Type:  workingset.ServerTypeImage,
					Image: "docker/server3:v1",
					Snapshot: &workingset.ServerSnapshot{
						Server: catalog.Server{
							Name: "beta-server",
						},
					},
				},
			},
		},
	}

	dbCat, err := catalogObj.ToDb()
	require.NoError(t, err)
	err = dao.UpsertCatalog(ctx, dbCat)
	require.NoError(t, err)

	output := captureStdout(t, func() {
		err := ListServers(ctx, dao, catalogObj.Ref, []string{}, workingset.OutputFormatJSON)
		require.NoError(t, err)
	})

	var result map[string]any
	err = json.Unmarshal([]byte(output), &result)
	require.NoError(t, err)

	servers := result["servers"].([]any)
	require.Len(t, servers, 3)

	// Verify servers are sorted alphabetically by name
	firstServer := servers[0].(map[string]any)
	snapshot := firstServer["snapshot"].(map[string]any)
	server := snapshot["server"].(map[string]any)
	assert.Equal(t, "alpha-server", server["name"])

	secondServer := servers[1].(map[string]any)
	snapshot = secondServer["snapshot"].(map[string]any)
	server = snapshot["server"].(map[string]any)
	assert.Equal(t, "beta-server", server["name"])

	thirdServer := servers[2].(map[string]any)
	snapshot = thirdServer["snapshot"].(map[string]any)
	server = snapshot["server"].(map[string]any)
	assert.Equal(t, "zebra-server", server["name"])
}

func TestParseFilters(t *testing.T) {
	tests := []struct {
		name        string
		filters     []string
		expected    []serverFilter
		expectError bool
		errorMsg    string
	}{
		{
			name:     "single filter",
			filters:  []string{"name=test"},
			expected: []serverFilter{{key: "name", value: "test"}},
		},
		{
			name:     "multiple filters",
			filters:  []string{"name=test", "type=image"},
			expected: []serverFilter{{key: "name", value: "test"}, {key: "type", value: "image"}},
		},
		{
			name:     "empty filters",
			filters:  []string{},
			expected: []serverFilter{},
		},
		{
			name:        "invalid filter format - no equals",
			filters:     []string{"invalid"},
			expectError: true,
			errorMsg:    "invalid filter format",
		},
		{
			name:        "invalid filter format - multiple equals",
			filters:     []string{"key=value=extra"},
			expected:    []serverFilter{{key: "key", value: "value=extra"}}, // SplitN allows this
			expectError: false,
		},
		{
			name:     "filter with empty value",
			filters:  []string{"name="},
			expected: []serverFilter{{key: "name", value: ""}},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := parseFilters(tt.filters)
			if tt.expectError {
				require.Error(t, err)
				assert.Contains(t, err.Error(), tt.errorMsg)
			} else {
				require.NoError(t, err)
				assert.Equal(t, tt.expected, result)
			}
		})
	}
}
